﻿using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Drill.TestSupport
{
	// *** HACK ALERT ***
	// This is a hack and should not be used in production code.
	// This code is intended to be used for tests only.
	// The fields that are being set are private and may change in 
	// future version of the .NET framework.
	// This code is based on code from Daniel Hilgarth posted to 
	// http://stackoverflow.com/questions/6150644/change-default-app-config-at-runtime

	public abstract class AppConfig : IDisposable
	{
		public static AppConfig Change(string path)
		{
			return new ChangeAppConfig(path);
		}

		public static AppConfig ChangeToEmptyConfig()
		{
			const string emptyConfigFileName = "Empty.config";
			string testDataDirectory = TestDataDirectoryLocator.FindTestDataDirectory();
			string path = Path.Combine(testDataDirectory, emptyConfigFileName);
			return new ChangeAppConfig(path);
		}

		public static void ResetConfigurationManager()
		{
			// Reset System.Configuration.ConfigurationManager.s_initState
			Type type = typeof(ConfigurationManager);
			FieldInfo field = type.GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
			if (null != field)
				field.SetValue(null, 0);

			// Reset System.Configuration.ConfigurationManager.s_configSystem
			field = type.GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
			if (null != field)
				field.SetValue(null, null);

			// Reset System.Configuration.ClientConfigPaths.s_current
			type = type.Assembly.GetTypes().First(t => t.FullName == "System.Configuration.ClientConfigPaths");
			field = type.GetField("s_current", BindingFlags.NonPublic | BindingFlags.Static);
			if (null != field)
				field.SetValue(null, null);
		}

		public abstract void Dispose();

		private class ChangeAppConfig : AppConfig
		{
			private readonly string _oldConfig =
				AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString();

			private bool _isDisposed;

			public ChangeAppConfig(string path)
			{
				AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
				ResetConfigurationManager();
			}

			public override void Dispose()
			{
				if (!_isDisposed)
				{
					AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", _oldConfig);
					ResetConfigurationManager();


					_isDisposed = true;
				}
				GC.SuppressFinalize(this);
			}
		}
	}
}